#include "Peer.h"

///-----------------------------------------------------------------------
///						Constructors
///-----------------------------------------------------------------------

Peer::Peer()
{
	peerData = RakPeerInterface::GetInstance();
	portNumber = LISTENING_PORT;
	newToNetwork = false;
	SocketDescriptor peerSocket;
	peerSocket = SocketDescriptor(LISTENING_PORT, 0);
	peerData->Startup(MAXIMUM_AMOUNT_NEIGHBORS, &peerSocket, 1);

	//set maximum number of connections
	peerData->SetMaximumIncomingConnections(MAXIMUM_AMOUNT_NEIGHBORS);

	cout << "Peer established" << endl;
	running = true;
	//spawn update thread
	ListenOnNetwork();

}

Peer::Peer(std::string ipAddress, int socketNumber)
{
	peerData = RakPeerInterface::GetInstance();
	portNumber = LISTENING_PORT + socketNumber;
	bootStrapAddress = ipAddress;
	newToNetwork = true;
	SocketDescriptor peerSocket;
	peerSocket = SocketDescriptor(LISTENING_PORT + socketNumber, 0);
	peerData->Startup(MAXIMUM_AMOUNT_NEIGHBORS, &peerSocket, 1);

	//set maximum number of connections
	peerData->SetMaximumIncomingConnections(MAXIMUM_AMOUNT_NEIGHBORS);
	running = true;
	cout << "Peer established" << endl;
	//spawn update thread
	ListenOnNetwork();
}

Peer::Peer(std::string ipAddress, int socketNumber, std::string nameAssigned)
{
	peerData = RakPeerInterface::GetInstance();
	portNumber = LISTENING_PORT + socketNumber;
	bootStrapAddress = ipAddress;
	newToNetwork = true;
	SocketDescriptor peerSocket;
	peerSocket = SocketDescriptor(LISTENING_PORT + socketNumber, 0);
	peerData->Startup(MAXIMUM_AMOUNT_NEIGHBORS, &peerSocket, 1);

	//set maximum number of connections
	peerData->SetMaximumIncomingConnections(MAXIMUM_AMOUNT_NEIGHBORS);
	running = true;
	updater = PeerSimulator(nameAssigned, socketNumber);
	cout << "Peer " <<nameAssigned<<" established" << endl;
	//spawn update thread
	ListenOnNetwork();
}

Peer::Peer(int socketNumber)
{
	peerData = RakPeerInterface::GetInstance();
	portNumber = socketNumber;
	newToNetwork = true;
	SocketDescriptor peerSocket;
	peerSocket = SocketDescriptor(LISTENING_PORT + socketNumber,0);
	peerData->Startup(MAXIMUM_AMOUNT_NEIGHBORS, &peerSocket, 1);

	//set maximum number of connections
	peerData->SetMaximumIncomingConnections(MAXIMUM_AMOUNT_NEIGHBORS);

	cout << "Peer established" << endl;
	running = true;
	//spawn update thread
	ListenOnNetwork();
}


//test constructor
Peer::Peer(std::string bootStrapIdAddress)
{
	peerData = RakPeerInterface::GetInstance();
	//define the maximhum almount of connections that  apeer can have 
	newToNetwork = true;
	//AddNeighbor(BOOT_STRAP_PEER_ID, bootStrapIdAddress);
	ListenOnNetwork();
}

///-----------------------------------------------------------------------
///						Destructors
///-----------------------------------------------------------------------

Peer::~Peer()
{
	RakPeerInterface::DestroyInstance(peerData);
}

///-----------------------------------------------------------------------
///						HandShakeRequest 
///-----------------------------------------------------------------------

void Peer::HandShakeRequest(SystemAddress& address)
{
	BitStream bS;
	bS.Write((RakNet::MessageID)BOOT_HAND_SHAKE_REQUEST);
	SendData(address, bS);
}

///-----------------------------------------------------------------------
///						DealHandShake 
///-----------------------------------------------------------------------

void Peer::DealHandShake(SystemAddress &address, RakNetGUID &ddress)
{
	//add bootstrap as a neighbour
	AddNeighbor(ddress, address);

	BitStream bS;
	bS.Write((RakNet::MessageID)RECIEVE_BOOT_STRAP_REPLY);
	std::string output = GetRandomNeighbors();
	bS.Write(output.c_str());
	peerData->Send(&bS, HIGH_PRIORITY, RELIABLE_ORDERED, 0, address, false);
}

///-----------------------------------------------------------------------
///						AddNeighbour
///-----------------------------------------------------------------------

void Peer::AddNeighbor(RakNetGUID& peerName, SystemAddress& address)
{
	if (!FindNeighbour(address) && peerName != peerData->GetMyGUID())
	{
		if (neighbors.size() >= MAXIMUM_AMOUNT_NEIGHBORS)
		{
			//LRU technique to be used
			//lock
			SortCache();
			RemoveNeighbour(neighbors.size() - 1);
		}

		neighbors.push_back(Neighbour(address, peerName));
	}
}

///-----------------------------------------------------------------------
///						RemoveNeighbour
///-----------------------------------------------------------------------

void Peer::RemoveNeighbour(int index)
{
	neighbors.erase(neighbors.begin()+index);
}


///-----------------------------------------------------------------------
///						FindNeighbour
///-----------------------------------------------------------------------

bool Peer::FindNeighbour(SystemAddress& address)
{
	vector<Neighbour>::iterator iter = std::find(neighbors.begin(), neighbors.end(), Neighbour(address, RakNetGUID()));

	if (iter != neighbors.end() && (*iter).address == address)
		return true;

	return false;
}



///-----------------------------------------------------------------------
///						ListenOnNetwork 
///-----------------------------------------------------------------------

void Peer::ListenOnNetwork()
{
	Packet *packet;
	//update thread
	//std::thread updateThread(&Peer::SendUpdate, this);
	//InitPool();
	HelperClass helperClass;


	if (newToNetwork)
	{
		peerData->Connect(bootStrapAddress.c_str(), LISTENING_PORT, 0, 0);
	}

	while (1)
	{
		for (packet = peerData->Receive(); packet; peerData->DeallocatePacket(packet), packet = peerData->Receive())
		{
			switch (packet->data[0])
			{
			case ID_REMOTE_DISCONNECTION_NOTIFICATION:
				printf("Another client has disconnected.\n");
				break;
			case ID_REMOTE_CONNECTION_LOST:
				printf("Another client has lost the connection.\n");
				break;
			case ID_REMOTE_NEW_INCOMING_CONNECTION:
				printf("Another client has connected.\n");
				break;
				//connection accepted by a peer
			case ID_CONNECTION_REQUEST_ACCEPTED:
				{
					printf("Our connection request has been accepted");

					//check if we need to exchnage neighbors
					if (newToNetwork)
					{
						AddNeighbor(packet->guid, packet->systemAddress);
						HandShakeRequest(packet->systemAddress);
						newToNetwork = !newToNetwork;
					}
				}
				printf("Our connection request has been accepted.\n");
				break;
			case ID_NEW_INCOMING_CONNECTION:
				printf("A connection is incoming %s .\n", packet->systemAddress.ToString());
				break;
			case ID_NO_FREE_INCOMING_CONNECTIONS:
				printf("The server is full .\n");
				break;
			case ID_DISCONNECTION_NOTIFICATION:
				printf("We have been disconnected.\n");
				break;
			case ID_CONNECTION_LOST:
				printf("Connection lost.\n");
				//RemoveNeighbour(packet->systemAddress.ToString());
				break;
				//recieving update from bootstrpa request 
			case RECIEVE_BOOT_STRAP_REPLY:
				{
					Packet currentPacket = *packet;
					DealWithBootStrap(currentPacket);
					if ((portNumber - LISTENING_PORT) == 4)
					{
						RunUpdateProcedure();
					}
				}
				break;
				//recieving a handshake request
			case  BOOT_HAND_SHAKE_REQUEST:
				{
					DealHandShake(packet->systemAddress, packet->guid);
				}
				break;
			case HAND_SHAKE_INIT_UPDATE:
				{
					UpdateHandShakeInit(GetStringFromBitStream(*packet), packet->systemAddress);
				}
				break;
			case HAND_SHAKE_REPLY_PEER:
				{
					//recieve peers parse them 
					UpdatePeerReplyer(GetStringFromBitStream(*packet), packet->systemAddress);
				}
				break;
			case SHARE_LOCAL_UPDATES:
				{
					ProcessUpdatesRecieved(GetStringFromBitStream(*packet), packet->systemAddress);
				}
				break;

			case SHARE_LOCAL_UPDATES_REPLY:
				{
					ReplyToUpdate(GetStringFromBitStream(*packet), packet->systemAddress);
				}
				break;

			case TEST_MESSAGE:
				printf("Incoming message :) %s");
			default:
				printf("Message with identifier %i has arrived. %s \n", packet->data[0], packet->data);
				break;
			}
		}
	}
}

///-----------------------------------------------------------------------
///						GetRandomNeighbours
///-----------------------------------------------------------------------

Neighbour& Peer::GetRandomNeighbour()
{
	int randomNumber = rand() % neighbors.size();
	return neighbors[randomNumber];
}

///-----------------------------------------------------------------------
///						RemoveNeighbour
///-----------------------------------------------------------------------

void Peer::RemoveNeighbour(std::string neighboursAddress)
{
	vector<Neighbour>::iterator iter = std::find(neighbors.begin(), neighbors.end(), Neighbour(SystemAddress(neighboursAddress.c_str()), RakNetGUID()));

	if (iter != neighbors.end())
	{
		int index = iter - neighbors.begin();
		if (index < neighbors.size())
			RemoveNeighbour(index);
	}
}

///-----------------------------------------------------------------------
///						RequestConnection 
///-----------------------------------------------------------------------

void Peer::RequestConnection(std::string ipAddress)
{
	peerData->Connect(ipAddress.c_str(), LISTENING_PORT, 0, 0);
}

///-----------------------------------------------------------------------
///						GetNeighbour
///-----------------------------------------------------------------------

Neighbour& Peer::GetNeighbour(int index)
{
	if (index < neighbors.size() || index > 0)
	{
		return neighbors[index];
	}

	return Neighbour();
}


Neighbour& Peer::GetNeighbour(SystemAddress& address)
{
	vector<Neighbour>::iterator iter = std::find(neighbors.begin(), neighbors.end(), Neighbour(address, RakNetGUID()));
	return *iter;
	
}

///-----------------------------------------------------------------------
///						SendUpdate 
///-----------------------------------------------------------------------

void Peer::SendUpdate()
{
	while (running)
	{
		updater.ProduceUpdate(GetCurrentLogicalTime());
		std::string localUpdates = ParseNeighbours();
		updatesExist = false;
		RakNet::ConnectionAttemptResult result;
		Neighbour details;
		do
		{
			details = GetRandomNeighbour();

			std::string addressArray[2];
			HelperClass::ParseStringSentence(details.address.ToString(), "|", addressArray);
			result = peerData->Connect(addressArray[0].c_str(), boost::lexical_cast<unsigned short>(addressArray[1]), 0, 0);

		} while (result != 0);

		SendData(details, localUpdates, HAND_SHAKE_INIT_UPDATE);
		peerData->CloseConnection(details.address, true, 0, HIGH_PRIORITY);
		std::this_thread::sleep_for(std::chrono::microseconds(HelperClass::SetAmounTimeSleep(FIXED_AMOUNT_PEERS)* 100));
	}
}

///-----------------------------------------------------------------------
///						SendData 
///-----------------------------------------------------------------------

void Peer::SendData(SystemAddress& ipAddress, BitStream& bS)
{
	//temp 
	std::lock_guard<std::mutex> lk(myMutex);
	peerData->Send(&bS, HIGH_PRIORITY, RELIABLE_ORDERED, 0, ipAddress, false);
}

void Peer::SendData(Neighbour& currentNeighbour, BitStream& bS)
{
	SendData(currentNeighbour.address, bS);
}

void Peer::SendData(Neighbour& currentNeighbour, std::string& stringToUse, MessageID id)
{
	BitStream bS;
	bS.Write(MessageID(id));
	bS.Write(stringToUse.c_str());
	SendData(currentNeighbour.address, bS);
}

void Peer::SendData(SystemAddress& address, std::string& stringToUse, MessageID id)
{
	BitStream bS;
	bS.Write(MessageID(id));
	bS.Write(stringToUse.c_str());
	SendData(address, bS);
}


///-----------------------------------------------------------------------
///						UseUpdate
///-----------------------------------------------------------------------

void Peer::UseUpdate(std::string update, SystemAddress& address, int& connectionDetails)
{
	//add update to local daasrycture
	//_____________________________________________
}


///-----------------------------------------------------------------------
///						GetLocalUpdates
///-----------------------------------------------------------------------

std::string Peer::GetLocalUpdates()
{
	return "";
}

///-----------------------------------------------------------------------
///						DealWithBootStrap
///-----------------------------------------------------------------------

void Peer::DealWithBootStrap(Packet& packet)
{
	RakString rs;
	BitStream bsIn(packet.data, packet.length, false);
	bsIn.IgnoreBytes(sizeof(RakNet::MessageID));
	bsIn.Read(rs);
	const char * temp = rs.C_String();
	boost::char_separator<char> sep(";");
	std::string output = std::string(temp);
	tokenizer tokens(output, sep);
	
	for (tokenizer::iterator iter = tokens.begin(); iter != tokens.end(); iter++)
	{
		ParseCurrentNeighbor(*iter);
	}
	int size = neighbors.size();
}


///-----------------------------------------------------------------------
///						SortCache
///-----------------------------------------------------------------------

void Peer::SortCache()
{
	std::sort(neighbors.begin(), neighbors.end());
}


///-----------------------------------------------------------------------
///						LogicalTimer
///-----------------------------------------------------------------------

void Peer::IncrementLogicalTime()
{
	logicalTimer++;
}

int& Peer::GetCurrentLogicalTime()
{
	return logicalTimer;
}

///-----------------------------------------------------------------------
///						ParseNeighbours
///-----------------------------------------------------------------------

std::string Peer::ParseNeighbours()
{
	std::string output = "";
	for (int index = 0; index < neighbors.size(); index++)
	{
		output += neighbors[index].ToString();
	}
	return output;
}

void Peer::AddNewNeighbours(std::string& stringNeighbours)
{
	boost::char_separator<char> sep(";");
	std::string output = std::string(stringNeighbours);
	tokenizer tokens(output, sep);

	for (tokenizer::iterator iter = tokens.begin(); iter != tokens.end(); iter++)
	{
		ParseCurrentNeighbor(*iter);
	}
}


///-----------------------------------------------------------------------
///						UpdateNeighbours
///-----------------------------------------------------------------------

void Peer::UpdateHandShakeInit(std::string& peersString, SystemAddress& address)
{
	//update time with respective timer 

	IncrementLogicalTime();
	SetTimerNeighbour(GetCurrentLogicalTime(), address);
	AddNewNeighbours(peersString);
	std::string stringNeighbours = ParseNeighbours();
	SendData(address, stringNeighbours, HAND_SHAKE_REPLY_PEER);
}

void Peer::UpdatePeerReplyer(std::string& recievedUpdate, SystemAddress& address)
{
	IncrementLogicalTime();
	SetTimerNeighbour(GetCurrentLogicalTime(), address);
	AddNewNeighbours(recievedUpdate);
	std::string toReturnUpdates = updater.ParseUpdates();
	SendData(address, toReturnUpdates, SHARE_LOCAL_UPDATES);
}

void Peer::ProcessUpdatesRecieved(std::string& recievedUpdates, SystemAddress& address)
{
	IncrementLogicalTime();
	updater.UseRecivedUpdates(recievedUpdates);
	std::string toReturnUpdates = updater.ParseUpdates();
	SendData(address, toReturnUpdates, SHARE_LOCAL_UPDATES);
}

void Peer::ReplyToUpdate(std::string& recievedUpdates, SystemAddress& address)
{
	IncrementLogicalTime();
	updater.UseRecivedUpdates(recievedUpdates);
}


///-----------------------------------------------------------------------
///						SetTimerForNeighbour
///-----------------------------------------------------------------------

void Peer::SetTimerNeighbour(int& timer, SystemAddress& address)
{
	if (FindNeighbour(address))
	{
		Neighbour& currentNeighbour = GetNeighbour(address);
		currentNeighbour.lastConnection = timer;
	}
}


///-----------------------------------------------------------------------
///						SetTimerForNeighbour
///-----------------------------------------------------------------------

void Peer::RunUpdateProcedure()
{
	updater.ProduceUpdate(GetCurrentLogicalTime());
	std::string localPeers = ParseNeighbours();
	updatesExist = false;
	RakNet::ConnectionAttemptResult result;
	Neighbour details;
	do
	{
		details = GetRandomNeighbour();

		std::string addressArray[2];
		HelperClass::ParseStringSentence(details.address.ToString(), "|", addressArray);
		result = peerData->Connect(addressArray[0].c_str(), boost::lexical_cast<unsigned short>(addressArray[1]), 0, 0);

	} while (result != 0);

	SendData(details, localPeers, HAND_SHAKE_INIT_UPDATE);
	peerData->CloseConnection(details.address, true, 0, HIGH_PRIORITY);
	//std::this_thread::sleep_for(std::chrono::microseconds(HelperClass::SetAmounTimeSleep(FIXED_AMOUNT_PEERS) * 100));
}

///-----------------------------------------------------------------------
///						WriteToPeer 
///-----------------------------------------------------------------------

bool Peer::WriteToPeer(std::string peerId, std::string toWrite, PeerMessages peerMessage)
{
	try
	{
		BitStream bS;
		bS.Write((RakNet::MessageID)peerMessage);
		bS.Write(toWrite.c_str());
		SendData(SystemAddress(peerId.c_str()), bS);
	}
	catch (std::exception e)
	{
		return false;
	}
}

///-----------------------------------------------------------------------
///						Helper Functions 
///-----------------------------------------------------------------------

std::string Peer::GetRandomNeighbors()
{
	int amountOfNeighbours = MAX_AMOUNT_PEER_TO_SEND;
	std::string output = "";

	if (neighbors.size() < amountOfNeighbours)
	{
		for (int index = 0; index < neighbors.size(); ++index)
		{
			output = output + neighbors[index].ToString() + ";";
		}
	}
	else
	{
		vector<int> toSend;
		for (int index = 0; index < MAX_AMOUNT_PEER_TO_SEND; ++index)
		{
			bool notContained = false;
			do
			{
				notContained = false;
				int randomNumber = rand() % neighbors.size();
				
				
			} while (!notContained);
		}

		for (int index = 0; index < MAX_AMOUNT_PEER_TO_SEND; ++index)
		{
			output = output + neighbors[index].ToString() + ";";
		}
	}
	return output;
}

///-----------------------------------------------------------------------
///						InitPool
///-----------------------------------------------------------------------
void Peer::InitPool()
{
	/*int amountOfThreads = std::thread::hardware_concurrency();
	amountOfThreads -= 2;*/
	//threadPool = pool(1);
	//threadPool.create_thread(boost::bind(&io_service::run, &ioService));
	
	/*
	* This will assign tasks to the thread pool.
	* More about boost::bind: "http://www.boost.org/doc/libs/1_54_0/libs/bind/bind.html#with_functions"
	*/

	//adding tasks to queue
	//ioService.post(boost::bind(myTask, "Hello World!"));

}


void Peer::InitListener()
{
	//listenerThread = std::thread(&ListenOnNetwork);
}

///-----------------------------------------------------------------------
///						PasrseCurrentNeighbour
///-----------------------------------------------------------------------
//GUID-SystemAddress
void Peer::ParseCurrentNeighbor(std::string currentNeighbor)
{
	std::string myArray[2];
	HelperClass::ParseStringSentence(currentNeighbor, "-", myArray);
	AddNeighbor(RakNetGUID(std::atoi(myArray[0].c_str())), SystemAddress(myArray[1].c_str()));
}

void Peer::ParseSystemAddress(SystemAddress& address, std::string array[])
{
	HelperClass::ParseStringSentence(address.ToString(), "|", array);
}






















